package xj.graph2d;

import static xj.graph2d.DrawShapeConstants.HOTSPOT_NODE_LABEL;
import static xj.graph2d.DrawShapeConstants.HOTSPOT_NONE;
import static xj.graph2d.DrawShapeConstants.NODE_LABEL_ROLES;
import static xj.graph2d.DrawShapeConstants.PointOnMessage;
import static xj.graph2d.DrawShapeConstants.PointOutside;
import static xj.graph2d.DrawShapeConstants.ROLE_NODE_LABEL;
import static xj.graph2d.DrawShapeConstants.ROLE_NODE_TEMPLATE;
import static xj.graph2d.DrawShapeConstants.framePad;
import static xj.util.xml.XMLUtil.getXMLAttributeString;

import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.Dimension2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.PrintWriter;
import java.util.Set;

import javax.swing.Icon;
import javax.swing.SwingConstants;

import xj.graph2d.graph.AbstractGraphNode;
import xj.graph2d.graph.AbstractHyperGraphNode;
import xj.graph2d.graph.Connector;
import xj.graph2d.graph.Dimension2DFloat;
import xj.graph2d.graph.Geometry;
import xj.graph2d.graph.Graph;
import xj.graph2d.graph.GraphConstants;
import xj.graph2d.graph.GraphNode;
import xj.lang.CodeWriter;

import static xj.graph2d.graph.Geometry.CHECK;

public abstract class GraphNodeBase 
  extends GraphComponentShape 
  implements GraphNode<GraphEdgeBase> {

  public static final Dimension2D zeroDimension = new Dimension2DFloat(0, 0);

  protected GraphNodeBase() {
    super(-1); // no graph component
    clipSubshapes = true;
    acceptLabel = true;
    allowEdgeOffset = true;
    onBoundary = false;
  }

  protected GraphNodeBase(int comptype) {
    super(comptype);
    clipSubshapes = true;
    acceptLabel = true;
    allowEdgeOffset = true;
    onBoundary = false;
  }

  protected GraphNodeBase(boolean createComp) {
    super(createComp ? GraphConstants.NODE : -1);
    clipSubshapes = true;
    acceptLabel = true;
    allowEdgeOffset = true;
    onBoundary = false;
  }

  protected GraphNodeBase(String name) {
    super(GraphConstants.NODE);
    comp.setName(name);
    clipSubshapes = true;
    acceptLabel = true;
    allowEdgeOffset = true;
    onBoundary = false;
  }

  @Override
  public void initComponent(String compid) {
    if (acceptSubshape) {
      comp = new AbstractHyperGraphNode();
    } else {
      comp = new AbstractGraphNode();
    }
    if (comp != null && compid != null) {
      comp.setId(compid);
    }
  }

  @Override
  public void copyTo(DrawShapeBase s) 
    throws CloneNotSupportedException {
    super.copyTo(s);
    if (s instanceof GraphNodeBase) {
      GraphNodeBase sn = (GraphNodeBase) s;
      sn.nodePad = nodePad;
      sn.fitLabel = fitLabel;
      sn.allowEdgeOffset = allowEdgeOffset;
      sn.onBoundary = onBoundary;
      sn.labelPosition = labelPosition;
      sn.allowLabelOutside = allowLabelOutside;
      sn.boundaryPosition = boundaryPosition;

      if (labelListener != null) {
	try {
	  sn.setLabelListener((LabelListener) labelListener.clone());
	} catch (CloneNotSupportedException e) {
	  sn.labelListener = null;
	}
      }
    }
  }

  @Override
  public void drawOverHint(Graphics2D g2) {
    //Shape shape = getBoundingShape();
    Shape shape = getOutlineShape();
    if (shape != null) {
      g2.fill(shape);
    }
  }

  @Override
  public int isInside(float px, float py) {
    if (isOnMessage(px, py)) {
      return PointOnMessage;
    }
    return super.isInside(px, py);
  }

  /*
   * Return the intersection point of p and the center of the node The
   * default implementation assumes the node is a rectangle.
   */
  public Point2D intersect(Point2D p) {
    return intersect(p, 0, 0);
  }

  public Point2D intersect(Point2D p, float offsetX, float offsetY) {
    if (p != null) {
      Point2D p1;
      Point2D c = getCenter();
      c.setLocation(c.getX() + offsetX, c.getY() + offsetY);
      //if (isInside(p) == PointOutside) {
      if (!Geometry.inBox(p, getULCorner(), getLRCorner())) {
	// find the intersection closer to p
	p1 = Geometry.intersectionRectLine(getULCorner(), getLRCorner(), p, c);
	// handle the template class node
	DrawShape tshape = findTemplateShape();
	if (tshape != null) {
	  Point2D tp1 = tshape.getEnd1();
	  Point2D tp2 = tshape.getEnd2();
	  Point2D p2 = Geometry.intersectionRectLine(tp1, tp2, p, c);
	  if (p2 != null && Geometry.inBox(p2, tp1, tp2)) {
	    double d1 = Geometry.distance(p, p1);
	    double d2 = Geometry.distance(p, p2);
	    if (d2 < d1) {
	      p1 = p2;
	    }
	  }
	}
	
	//CHECK(p1);
	return p1;
      } else {
	// find the intersection closer to c
	p1 = Geometry.intersectionRectLine(getULCorner(), getLRCorner(), c, p);
	//CHECK(p1);
	return p1;
      }
    } else {
      return null;
    }
  }

  @Override
  public int isOnHotSpot(float px, float py) {
    if (isInside(px, py) != PointOutside) {
      return HOTSPOT_NODE_LABEL;
    }
    return HOTSPOT_NONE;
  }

  public Dimension2D getContentDimension() {
    Label label = findLabel();
    if (label != null) {
      //setLabel();
      Rectangle2D b = label.getBounds();
      float tw = (float) b.getWidth();
      float th = (float) b.getHeight();
      return new Dimension2DFloat(tw, th);
    } else {
      return zeroDimension;
    }
  }

  public boolean isFitLabel() {
    return fitLabel;
  }

  public void setFitLabel(boolean f) {
    fitLabel = f;
  }

  public float getNodePad() {
    return nodePad;
  }

  public void setNodePad(float nodePad) {
    this.nodePad = nodePad;
  }

  public Dimension2D fitContentDimension() {
    Dimension2D dim = getContentDimension();
    float w = 0;
    float h = 0;
    if (dim != null) {
      w = (float) dim.getWidth();
      h = (float) dim.getHeight();
    }
    return new Dimension2DFloat(w + 2 * nodePad, h + 2 * nodePad);
  }

  public boolean isAllowLabelOutside() {
    return allowLabelOutside;
  }

  public void setAllowLabelOutside(boolean allowLabelOutside) {
    this.allowLabelOutside = allowLabelOutside;
  }

  public boolean isAllowEdgeOffset() {
    return allowEdgeOffset;
  }

  public void setAllowEdgeOffset(boolean allowEdgeOffset) {
    this.allowEdgeOffset = allowEdgeOffset;
  }

  public boolean isOnBoundary() {
    return onBoundary;
  }

  public void setOnBoundary(boolean onBoundary) {
    this.onBoundary = onBoundary;
  }

  public String getNodeDisplayName() { 
    Label label = getLabelShape();
    if (label != null) { 
      return label.getDisplayText();
    }
    return "";
  }

  public boolean hasLabel() {
    Label label = findLabel();
    return (label != null);
  }

  public String getLabel() {
    return getLabelString();
  }

  public void setLabel(String label) {
    setLabelString(label);
  }

  public String getLabelString() {
    Label label = findLabel();
    if (label != null) {
      return label.getText();
    }
    return null;
  }

  public void setLabelString(String labelText) {
    Label label = findLabel();
    if (label == null) {
      TextShape text = new TextShape(labelText);
      if (labelListener != null) {
	text.addLabelListener(labelListener);
      }
      label = text;
      label.setRole(ROLE_NODE_LABEL);
      addShape(label);
    } else {
      label.setText(labelText);
    }
    positionLabel();
  }

  public Label removeLabel() {
    Label label = findLabel();
    if (label != null) {
      label.setOffset(0, 0);
      removeShape(label);
    }
    return label;
  }

  @Override
  public Label removeLabel(int role) {
    Label label = findLabel(role);
    if (label != null) {
      label.setOffset(0, 0);
      removeShape(label);
    }
    return label;
  }

  @Override
  public void setLabel(Label label) {
    setLabel(label, false);
  }

  public void setLabel(Label label, boolean offset) {
    removeLabel();
    if (label != null) {
      if (labelListener != null) {
	label.addLabelListener(labelListener);
      }
      addShape(label);
      if (offset) {
	setLabelOffset();
      } else {
	positionLabel();
      }
    }
  }

  @Override
  public Label getLabelShape() {
    return findLabel();
  }

  public void resetLabelOffset() {
    Label label = findLabel();
    if (label != null) {
      label.setOffset(0, 0);
      positionLabel();
    }
  }

  public void moveLabelOffset(float dx, float dy) {
    Label label = findLabel();
    if (label != null) {
      label.adjustOffset(dx, dy);
    }
  }

  @Override
  public void setLabelOffset(int role) {
    Label label = findLabel(role);
    if (label != null) {
      setLabelOffset();
    }
  }

  public void setLabelOffset() {
    float x = (float) (Math.min(p1.getX(), p2.getX()));
    float y = (float) (Math.min(p1.getY(), p2.getY()));
    float w = (float) (Math.abs(p1.getX() - p2.getX()));
    float h = (float) (Math.abs(p1.getY() - p2.getY()));
    setLabelOffset(x, y, w, h);
  }

  protected void setLabelOffset(Rectangle2D rect) {
    if (rect != null) {
      float x = (float) rect.getX();
      float y = (float) rect.getY();
      float w = (float) rect.getWidth();
      float h = (float) rect.getHeight();
      setLabelOffset(x, y, w, h);
    }
  }

  protected void setLabelOffset(float x, float y, float w, float h) {
    Label label = findLabel();
    if (label != null) {
      Rectangle2D b = label.getBounds();
      float tx = (float) b.getX();
      float ty = (float) b.getY();
      float tw = (float) b.getWidth();
      float th = (float) b.getHeight();
      boolean moved = false;

      if (!allowLabelOutside) {
	if (tx < x) {
	  tx = x;
	  moved = true;
	}
	if (tx + tw > x + w) {
	  tx = x + w - tw;
	  moved = true;
	}
	if (ty < y) {
	  ty = y;
	  moved = true;
	}
	if (ty + th > y + h) {
	  ty = y + h - th;
	  moved = true;
	}
      }
      if (moved) {
	// System.out.println("GraphNodeBase.setLabelOffset() @2 moved
	// == true");
	label.setPosition(tx, ty);
      }
      float labelOffsetX = 0f;
      float labelOffsetY = 0f;
      if (w != 0) {
	labelOffsetX = ((tx * 2 + tw) - (x * 2 + w)) / w;
      }
      if (h != 0) {
	labelOffsetY = ((ty * 2 + th) - (y * 2 + h)) / h;
      }
      label.setOffset(labelOffsetX, labelOffsetY);
    }
  }

  protected Label findLabel() {
    int n = NODE_LABEL_ROLES.length;
    for (int i = 0; i < n; i++) {
      int role = NODE_LABEL_ROLES[i];
      DrawShape s = findInRole(role);
      if (s instanceof Label) {
	return (Label) s;
      }
    }
    return null;
  }

  protected void setLabel() {
    String name = getName();
    if (name != null) {
      setLabelString(name);
    }
  }

  public void setLabelPosition(int pos) {
    labelPosition = pos;
  }

  public int getLabelPosition() {
    return labelPosition;
  }

  public void setBoundaryPosition(int pos) {
    boundaryPosition = pos;
  }

  public int getBoundaryPosition() {
    return boundaryPosition;
  }

  @Override
  public void positionLabel(int role) {
    positionLabel();
  }

  public void positionLabel() {
    if (debug) 
      System.out.println("GraphNodeBase.positionLabel()");

    Label label = findLabel();
    if (label != null) {
      if (fitLabel) { 
	adjustContent(false);
      }

      label.calculateBounds();

      if (bounds == null) {
	calculateBounds();
      }

      float x = (float) Math.min(p1.getX(), p2.getX());
      float y = (float) Math.min(p1.getY(), p2.getY());
      float w = (float) Math.abs(p1.getX() - p2.getX());
      float h = (float) Math.abs(p1.getY() - p2.getY());
      y += getTemplateYAdjust();

      if (labelPosition == LABEL_POSITION_ABOVE_TOP) {
	positionShape(label, x, y, w, h, SwingConstants.TOP, SwingConstants.CENTER, 
		      nodePad, nodePad, false);
      } else if (labelPosition == LABEL_POSITION_LEFT_ADJUST) {
	float hpad = nodePad;
	if (message != null) { 
	  Icon icon = message.getIcon();
	  hpad += (icon.getIconWidth() - framePad); 
	}
	positionShape(label, x, y, w, h, SwingConstants.TOP, SwingConstants.LEFT, 
		      nodePad, hpad, true);
      } else if (labelPosition == LABEL_POSITION_RIGHT_ADJUST) {
	positionShape(label, x, y, w, h, SwingConstants.TOP, SwingConstants.RIGHT, 
		      nodePad, nodePad, true);
      } else {
	// positionLabel(bounds);
	positionShape(label, x, y, w, h, label.getOffset());
      }
    }
  }

  protected void positionLabel(Rectangle2D rect) {
    Label label = findLabel();
    if (label != null && rect != null) {
      positionShape(label, rect, label.getOffset());
    }
  }

  protected void positionLabel(float x, float y, float w, float h) {
    Label label = findLabel();
    if (label != null) {
      positionShape(label, x, y, w, h, label.getOffset());
    }
  }

  /**
   * Position shape at the center of rect [x, y, w, h], when dx == 0 and
   * dy == 0 (dx, dy) offset to the center position, proportional to the w
   * and h dx, dy in range [-1.0, 1.0]
   */
  @Override
  public void positionShape(DrawShape shape, float x, float y, float w, float h, 
			    float dx, float dy) {
    if (shape != null) {
      Rectangle2D b = shape.getBounds();
      float sw = (float) b.getWidth();
      float sh = (float) b.getHeight();
      if (!(shape instanceof Label) || !allowLabelOutside) {
	if (dx < -1) {
	  dx = -1;
	}
	if (dx > 1) {
	  dx = 1;
	}
	if (dy < -1) {
	  dy = -1;
	}
	if (dy > 1) {
	  dy = 1;
	}
      }
      float px = x + (w - sw) / 2 + dx * w / 2;
      float py = y + (h - sh) / 2 + dy * h / 2;
      shape.setPosition(px, py);
    }
  }

  @Override
  public void layoutComponents() {
    if (debug) System.out.println("GraphNodeBase.layoutComponents()");

    positionLabel();
    positionSubNodes();
    positionBoundaryNodes();
  }

  @Override
  public boolean isLabel(DrawShape shape) {
    Label label = findLabel();
    return (shape != null && shape == label);
  }

  public LabelListener getLabelListener() {
    return labelListener;
  }

  public void setLabelListener(LabelListener labelListener) {
    this.labelListener = labelListener;
    if (labelListener instanceof ModelLabelListener) {
      ModelLabelListener ml = (ModelLabelListener) labelListener;
      ml.setModelListener(modelListener);
    }
  }

  public void setTemplateShape(DrawShape temp) {
    if (temp != null) {
      DrawShape s = findInRole(ROLE_NODE_TEMPLATE);
      while (s != null) {
	removeShape(s);
	s = findInRole(ROLE_NODE_TEMPLATE);
      }

      temp.setRole(ROLE_NODE_TEMPLATE);
      addShape(temp);
    }
  }

  public DrawShape findTemplateShape() {
    return findInRole(ROLE_NODE_TEMPLATE);
  }

  public void removeTemplateShape() {
    DrawShape temp = findInRole(ROLE_NODE_TEMPLATE);
    if (temp != null) {
      removeShape(temp);
    }
  }

  public static final int tpadx = 10;

  public static final int tpady = 10;

  public void positionTemplateShape() {
    // System.out.println("GraphNodeBase.positionTemplateShape()");

    DrawShape temp = findInRole(ROLE_NODE_TEMPLATE);
    if (temp != null) {
      float x1 = (float) Math.min(p1.getX(), p2.getX());
      float y1 = (float) Math.min(p1.getY(), p2.getY());
      float w = (float) Math.abs(p2.getX() - p1.getX());
      float h = (float) Math.abs(p2.getY() - p1.getY());
      Point2D tp1 = temp.getEnd1();
      float tx = (float) tp1.getX();
      float ty = (float) tp1.getY();
      float tw = temp.getShapeWidth();
      float th = temp.getShapeHeight();
      float dx = 0;
      float dy = 0;
      if (tw > (w / 2)) {
	dx = (x1 + w / 2 + tpadx) - tx;
      } else {
	dx = (x1 + w + tpadx - tw) - tx;
      }
      if (th > (h / 2 + tpady)) {
	dy = (y1 + h / 2 - th) - ty;
      } else {
	dy = (y1 - tpadx) - ty;
      }
      temp.move(dx, dy);
    }
  }

  public float getTemplateYAdjust() { 
    DrawShape temp = findInRole(ROLE_NODE_TEMPLATE);
    if (temp != null && 
	temp.isVisible()) {
      float th = temp.getShapeHeight();
      return (th / 2);
    }
    return 0f;
  }
  
  public float getTemplateXAdjust() { 
    DrawShape temp = findInRole(ROLE_NODE_TEMPLATE);
    if (temp != null && 
	temp.isVisible()) {
      float w = getShapeWidth();
      float tw = temp.getShapeWidth();
      if (tw > w / 2) {
	return (tw * 2 - w);	
      }
    }
    return 0f;
  }
  
  /*
   * Handle template shape. No cliping of the template shape, since it may
   * extend beyond the boundary.
   */
  /*
   * protected void drawSubShapes(Graphics2D g2, int mode, DrawShape
   * selectedShape, Set<DrawShape> selectionSet) { if (subshapes != null) {
   * if (clipSubshapes) { g2.setClip(getEndRect()); } int n =
   * subshapes.size();
   * 
   * DrawShape tempShape = null; for (int i = 0; i < n; i++) { DrawShape s =
   * (DrawShape) subshapes.get(i); if (s.isVisible()) { int role =
   * s.getRole(); if (role == ROLE_NODE_TEMPLATE) { tempShape = s; } else {
   * s.drawShape(g2, mode, selectedShape, selectionSet); } } } if
   * (clipSubshapes) { g2.setClip(null); }
   * 
   * if (tempShape != null) { tempShape.drawShape(g2, mode, selectedShape,
   * selectionSet); } } }
   */

  @Override
  public boolean isSubShapeClipped(DrawShape subshape) {
    if (subshape != null) {
      int role = subshape.getRole();
      if (role == ROLE_NODE_TEMPLATE) {
	return false;
      }
      if (subshape instanceof GraphNodeBase) {
	GraphNodeBase n = (GraphNodeBase) subshape;
	if (n.isOnBoundary()) {
	  return false;
	}
      }
    }
    return true;
  }

  public void positionBoundaryNodes() {
    if (subshapes != null) {
      int n = subshapes.size();
      for (int i = 0; i < n; i++) {
	DrawShape s = subshapes.get(i);
	if (s.isVisible() && s instanceof GraphNodeBase) {
	  GraphNodeBase node = (GraphNodeBase) s;
	  if (node.isOnBoundary()) {
	    positionNodeOnBoundary(node);
	  }
	}
      }
    }
  }

  public void positionNodeOnBoundary(GraphNodeBase node) {
    if (node != null && node.isOnBoundary()) {
      float dx = 0f;
      float dy = 0f;
      Point2D c = node.getCenter();
      float cx = (float) c.getX();
      float cy = (float) c.getY();
      float w = node.getShapeWidth();
      float h = node.getShapeHeight();

      float x1 = (float) Math.min(p1.getX(), p2.getX());
      float y1 = (float) Math.min(p1.getY(), p2.getY());
      float x2 = (float) Math.max(p1.getX(), p2.getX());
      float y2 = (float) Math.max(p1.getY(), p2.getY());
      float de = Math.abs(x2 - cx);
      float dw = Math.abs(x1 - cx);
      float dn = Math.abs(y1 - cy);
      float ds = Math.abs(y2 - cy);
      if (boundaryPosition == BOUNDARY_POSITION_DEFAULT) {
	/*
         * Point2D p = intersect(c); if (p != null) { float px = (float)
         * p.getX(); float py = (float) p.getY(); dx = px - cx; dy = py - cy; }
         */
	float min = Math.min(Math.min(de, dw), Math.min(ds, dn));
	if (min == de) {
	  dx = (x2 - cx);
	} else if (min == dw) {
	  dx = (x1 - cx);
	} else if (min == dn) {
	  dy = (y1 - cy);
	} else if (min == ds) {
	  dy = (y2 - cy);
	}
      } else {
	if (boundaryPosition == BOUNDARY_POSITION_EAST) {
	  dx = x2 - cx;
	} else if (boundaryPosition == BOUNDARY_POSITION_WEST) {
	  dx = x1 - cx;
	} else if (boundaryPosition == BOUNDARY_POSITION_NORTH) {
	  dy = y1 - cy;
	} else if (boundaryPosition == BOUNDARY_POSITION_SOUTH) {
	  dy = y2 - cy;
	} else if (boundaryPosition == BOUNDARY_POSITION_EAST_WEST) {
	  if (de < dw) {
	    dx = x2 - cx;
	  } else {
	    dx = x1 - cx;
	  }
	} else if (boundaryPosition == BOUNDARY_POSITION_NORTH_SOUTH) {
	  if (dn < ds) {
	    dy = y1 - cy;
	  } else {
	    dy = y2 - cy;
	  }
	} else if (boundaryPosition == BOUNDARY_POSITION_OUTSIDE) {
	  float min = Math.min(Math.min(de, dw), Math.min(ds, dn));
	  if (min == de) {
	    dx = (x2 + w / 2) - cx;
	  } else if (min == dw) {
	    dx = (x1 - w / 2) - cx;
	  } else if (min == dn) {
	    dy = (y1 - h / 2) - cy;
	  } else if (min == ds) {
	    dy = (y2 + h / 2) - cy;
	  }
	} else if (boundaryPosition == BOUNDARY_POSITION_OUTSIDE_EAST) {
	  dx = (x2 + w / 2) - cx;
	} else if (boundaryPosition == BOUNDARY_POSITION_OUTSIDE_WEST) {
	  dx = (x1 - w / 2) - cx;
	} else if (boundaryPosition == BOUNDARY_POSITION_OUTSIDE_NORTH) {
	  dy = (y1 - h / 2) - cy;
	} else if (boundaryPosition == BOUNDARY_POSITION_OUTSIDE_SOUTH) {
	  dy = (y2 + h / 2) - cy;
	} else if (boundaryPosition == BOUNDARY_POSITION_OUTSIDE_EAST_WEST) {
	  if (de < dw) {
	    dx = (x2 + w / 2) - cx;
	  } else {
	    dx = (x1 - w / 2) - cx;
	  }
	} else if (boundaryPosition == BOUNDARY_POSITION_OUTSIDE_NORTH_SOUTH) {
	  if (dn < ds) {
	    dy = (y1 - h / 2) - cy;
	  } else {
	    dy = (y2 + h / 2) - cy;
	  }
	}
      }

      if (dx != 0f || dy != 0f) {
	node.move(dx, dy);
	node.layoutAdjacentEdges();
      }
    }
  }

  public void positionSubNodes() {
    if (subshapes != null) {
      int n = subshapes.size();
      for (int i = 0; i < n; i++) {
	DrawShape s = subshapes.get(i);
	if (s.isVisible() && 
	    s instanceof GraphNodeBase) {
	  GraphNodeBase node = (GraphNodeBase) s;
	  if (!node.isOnBoundary() && 
	      node.getRole() != ROLE_NODE_TEMPLATE) {
	    positionSubNode(node);
	  }
	}
      }
    }
  }

  public boolean positionSubNode(GraphNodeBase node) {
    return positionSubNode(node, null);
  }

  // pos is the insertion location of the new node, 
  // return true if changed
  public boolean positionSubNode(GraphNodeBase node, Point2D pos) {
    if (node != null && !node.isOnBoundary()) {
      float x1 = (float) Math.min(p1.getX(), p2.getX());
      float y1 = (float) Math.min(p1.getY(), p2.getY());
      float x2 = (float) Math.max(p1.getX(), p2.getX());
      float y2 = (float) Math.max(p1.getY(), p2.getY());

      Rectangle2D b = node.getBounds();
      float nx = (float) b.getX();
      float ny = (float) b.getY();
      float nw = (float) b.getWidth();
      float nh = (float) b.getHeight();
      float dx = 0f, dy = 0f;
      if (nx < x1) {
	dx = (x1 + 10 - nx);
      } else if ((nx + nw) > x2) {
	dx = (x2 - (nx + nw) - 10);
      }
      if (ny < y1) {
	dy = (y1 + 10 - ny);
      } else if ((ny + nh) > y2) {
	dy = (y2 - (ny + nh) - 10);
      }
      if (dx != 0f || dy != 0f) {
	node.move(dx, dy);
	node.layoutAdjacentEdges();
      }
    }
    return false;
  }

  @Override
  public void doLayout() {
    super.doLayout();
    positionTemplateShape();
  }

  @Override
  protected void writeXMLElements(CodeWriter out, int mode,
      String namespacePrefix) {
    if (out != null) {
      super.writeXMLElements(out, mode, namespacePrefix);

      String nodename;
      nodename = getPrefixedName("GraphNode", namespacePrefix);
      out.println("<" + nodename + ' ' + 
		  getXMLAttributeString("pad", nodePad) + ' ' + 
		  getXMLAttributeString("fitLabel", fitLabel) + ' ' + 
		  getXMLAttributeString("label", acceptLabel) + ' ' + 
		  getXMLAttributeString("outside", allowLabelOutside) + ' ' + 
		  getXMLAttributeString("offset", allowEdgeOffset) + ' ' + 
		  getXMLAttributeString("boundary", onBoundary) + ' ' + 
		  getXMLAttributeString("bpos", boundaryPosition) + ' ' + 
		  getXMLAttributeString("pos", labelPosition) + "/>");
    }
  }

  @Override
  protected void processXMLElement(org.w3c.dom.Element e, String namespaceURI) {
    if (e != null) {
      String nodename = e.getLocalName();
      if ("GraphNode".equals(nodename)) {
	String s;
	s = e.getAttribute("pad");
	try {
	  nodePad = Integer.parseInt(s);
	} catch (NumberFormatException ex) {
	}

	s = e.getAttribute("fitLabel");
	fitLabel = Boolean.valueOf(s);
	s = e.getAttribute("label");
	acceptLabel = Boolean.valueOf(s);
	s = e.getAttribute("outside");
	allowLabelOutside = Boolean.valueOf(s);
	s = e.getAttribute("offset");
	allowEdgeOffset = Boolean.valueOf(s);
	s = e.getAttribute("boundary");
	onBoundary = Boolean.valueOf(s);

	s = e.getAttribute("pos");
	try {
	  labelPosition = Integer.parseInt(s);
	} catch (NumberFormatException ex) {
	}
	s = e.getAttribute("bpos");
	try {
	  boundaryPosition = Integer.parseInt(s);
	} catch (NumberFormatException ex) {
	}
      } else {
	super.processXMLElement(e, namespaceURI);
      }
    }
  }

  protected float nodePad = 5;

  protected boolean allowLabelOutside = false;

  protected boolean allowEdgeOffset = true;

  protected boolean onBoundary = false;

  protected boolean fitLabel = true;

  protected int labelPosition = LABEL_POSITION_CENTER;

  protected int boundaryPosition = BOUNDARY_POSITION_DEFAULT;

  protected LabelListener labelListener = null;

  public static final int LABEL_POSITION_DEFAULT = 0;

  public static final int LABEL_POSITION_CENTER = 0;

  public static final int LABEL_POSITION_ABOVE_TOP = 1;

  public static final int LABEL_POSITION_LEFT_ADJUST = 10;

  public static final int LABEL_POSITION_RIGHT_ADJUST = 11;


  public static final int BOUNDARY_POSITION_DEFAULT = 0; // center on the boundary

  public static final int BOUNDARY_POSITION_EAST = 1;

  public static final int BOUNDARY_POSITION_WEST = 2;

  public static final int BOUNDARY_POSITION_NORTH = 3;

  public static final int BOUNDARY_POSITION_SOUTH = 4;

  public static final int BOUNDARY_POSITION_EAST_WEST = 5;

  public static final int BOUNDARY_POSITION_NORTH_SOUTH = 6;

  public static final int BOUNDARY_POSITION_OUTSIDE = 10;

  public static final int BOUNDARY_POSITION_OUTSIDE_EAST = 11;

  public static final int BOUNDARY_POSITION_OUTSIDE_WEST = 12;

  public static final int BOUNDARY_POSITION_OUTSIDE_NORTH = 13;

  public static final int BOUNDARY_POSITION_OUTSIDE_SOUTH = 14;

  public static final int BOUNDARY_POSITION_OUTSIDE_EAST_WEST = 15;

  public static final int BOUNDARY_POSITION_OUTSIDE_NORTH_SOUTH = 16;

  // adapt to GraphNode

  @Override
  public void setName(String name) {
    super.setName(name);
    setLabel();
    adjustContent();
  }

  public void adjustContent() {
    adjustContent(true);
  }

  public void adjustContent(boolean adjustEdges) {
    float x = (float) Math.min(p1.getX(), p2.getX());
    float y = (float) Math.min(p1.getY(), p2.getY());
    float w = (float) Math.abs(p1.getX() - p2.getX());
    float h = (float) Math.abs(p1.getY() - p2.getY());
    Dimension2D dim = fitContentDimension();
    if (dim != null) {
      float dw = (float) dim.getWidth() - w;
      float dh = (float) dim.getHeight() - h;
      if (dw < 0) {
	dw = 0;
      }
      if (dh < 0) {
	dh = 0;
      }
      if (dw > 0 || dh > 0) {
	setEnds(x - dw / 2, y - dh / 2, x + w + dw / 2, y + h + dh / 2);
      }
    }
    if (adjustEdges) { 
      layoutAdjacentEdges();
    }
  }

  public void layoutAdjacentEdges() {
    Graph graph = getGraph();
    if (graph instanceof GraphDocument) {
      ((GraphDocument) graph).layoutAdjacentEdges(this);
    }
  }

  public int getType() {
    return GraphConstants.NODE;
  }

  @Override
  public boolean isNode() {
    return true;
  }

  public Point2D getULCorner() {
    return p1;
  }

  public Point2D getLRCorner() {
    return p2;
  }

  public Point2D getCenter() {
    return getCenterPosition();
    /*
    Point2D p = new Point2DFloat(0, 0);
    if (p1 != null && p2 != null) {
      p.setLocation((p1.getX() + p2.getX()) / 2, (p1.getY() + p2.getY()) / 2);
    }
    return p;
    */
  }

  public void setLocation(Point2D p) {
    setPosition(p);
  }

  public void setLocation(double x, double y) {
    setPosition((float) x, (float) y);
  }

  public Dimension2D getSize() {
    //return new xj.graph2d.graph.Dimension2DFloat((float) bounds.getWidth(), (float) bounds.getHeight());
    float w = (float) Math.abs(p1.getX() - p2.getX());
    float h = (float) Math.abs(p1.getY() - p2.getY());
    return new xj.graph2d.graph.Dimension2DFloat(w, h);
  }

  public void setSize(double width, double height) {
    setEnds((float) p1.getX(), (float) p1.getY(), 
	    (float) (p1.getX() + width), (float) (p1.getY() + height));
  }

  /*
  public float getShapeWidth() {
    DrawShape temp = findInRole(ROLE_NODE_TEMPLATE);
    float x1 = (float) p1.getX();
    float x2 = (float) p2.getX();
    float xmin =  Math.min(x1, x2);
    float xmax =  Math.max(x1, x2);
    if (temp != null && 
	temp.isVisible()) {
      Point2D tp1 = temp.getEnd1();
      Point2D tp2 = temp.getEnd2();
      float tx1 = (float) tp1.getX();
      float tx2 = (float) tp2.getX();
      float txmin =  Math.min(tx1, tx2);
      float txmax =  Math.max(tx1, tx2);
      xmin = Math.min(xmin, txmin);
      xmax = Math.max(xmax, txmax);
    }
    return xmax - xmin;
  }

  public float getShapeHeight() {
    DrawShape temp = findInRole(ROLE_NODE_TEMPLATE);
    float y1 = (float) p1.getY();
    float y2 = (float) p2.getY();
    float ymin =  Math.min(y1, y2);
    float ymax =  Math.max(y1, y2);
    if (temp != null && 
	temp.isVisible()) {
      Point2D tp1 = temp.getEnd1();
      Point2D tp2 = temp.getEnd2();
      float ty1 = (float) tp1.getY();
      float ty2 = (float) tp2.getY();
      float tymin =  Math.min(ty1, ty2);
      float tymax =  Math.max(ty1, ty2);
      ymin = Math.min(ymin, tymin);
      ymax = Math.max(ymax, tymax);
    }
    return ymax - ymin;
  }
  */

  @Override
  public String toString() {
    if (comp != null) {
      return comp.getName() + " " + bounds;
    } else {
      return super.toString();
    }
  }

  public int getNodeType() {
    if (comp instanceof GraphNode) {
      return ((GraphNode) comp).getNodeType();
    } else {
      return 0;
    }
  }

  public void setNodeType(int nodeType) {
    if (comp instanceof GraphNode) {
      ((GraphNode) comp).setNodeType(nodeType);
    }
  }

  public int getCount() {
    if (comp instanceof GraphNode) {
      return ((GraphNode) comp).getCount();
    } else {
      return 0;
    }
  }

  public void setCount(int count) {
    if (comp instanceof GraphNode) {
      ((GraphNode) comp).setCount(count);
    }
  }

  public int getHeight() {
    if (comp instanceof GraphNode) {
      return ((GraphNode) comp).getHeight();
    } else {
      return 0;
    }
  }

  public void setHeight(int height) {
    if (comp instanceof GraphNode) {
      ((GraphNode) comp).setHeight(height);
    }
  }

  public int getRank() {
    if (comp instanceof GraphNode) {
      return ((GraphNode) comp).getRank();
    } else {
      return 0;
    }
  }

  public void setRank(int rank) {
    if (comp instanceof GraphNode) {
      ((GraphNode) comp).setRank(rank);
    }
  }

  // from GraphNode
  public void move(double dx, double dy) {
    super.move((float) dx, (float) dy);
    /*
     * if (comp instanceof GraphNode) { ((GraphNode) comp).move(dx, dy); }
     */
  }

  // from DrawShape
  @Override
  public void move(float dx, float dy) {
    super.move(dx, dy);
    /*
     * if (comp instanceof GraphNode) { ((GraphNode) comp).move(dx, dy); }
     */
  }

  public Set getConnectors() {
    if (comp instanceof GraphNode) {
      return ((GraphNode) comp).getConnectors();
    }
    return null;
  }

  public void addConnector(Connector c) {
    if (comp instanceof GraphNode) {
      ((GraphNode) comp).addConnector(c);
    }
  }

  public void removeConnector(Connector c) {
    if (comp instanceof GraphNode) {
      ((GraphNode) comp).removeConnector(c);
    }
  }

  public Set leavingEdges() {
    if (comp instanceof GraphNode) {
      return ((GraphNode) comp).leavingEdges();
    }
    return null;
  }

  public Set arrivingEdges() {
    if (comp instanceof GraphNode) {
      return ((GraphNode) comp).arrivingEdges();
    }
    return null;
  }

  public int getDegree() {
    if (comp instanceof GraphNode) {
      return ((GraphNode) comp).getDegree();
    }
    return 0;
  }

  public int getInDegree() {
    if (comp instanceof GraphNode) {
      return ((GraphNode) comp).getInDegree();
    }
    return 0;
  }

  public int getOutDegree() {
    if (comp instanceof GraphNode) {
      return ((GraphNode) comp).getOutDegree();
    }
    return 0;
  }

  // get total count of subnodes (including this) 
  public int getNodeCount() { 
    int count = 1; 
    if (subshapes != null) { 
      int n = subshapes.size();
      for (int i = 0; i < n; i++) {
	DrawShape shape = subshapes.get(i);
	if (shape instanceof GraphNodeBase) {
	  GraphNodeBase node = (GraphNodeBase) shape; 
	  count += node.getNodeCount();
	}
      }
    }
    return count; 
  }

  public void printNode(PrintWriter out) {
  }

}
